{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 编辑距离的计算\n",
    "编辑距离可以用来计算两个字符串的相似度，它的应用场景很多，其中之一是拼写纠正（spell correction）。 编辑距离的定义是给定两个字符串str1和str2, 我们要计算通过最少多少代价cost可以把str1转换成str2. \n",
    "\n",
    "举个例子：\n",
    "\n",
    "输入:   str1 = \"geek\", str2 = \"gesek\"\n",
    "输出:  1\n",
    "插入 's'即可以把str1转换成str2\n",
    "\n",
    "输入:   str1 = \"cat\", str2 = \"cut\"\n",
    "输出:  1\n",
    "用u去替换a即可以得到str2\n",
    "\n",
    "输入:   str1 = \"sunday\", str2 = \"saturday\"\n",
    "输出:  3\n",
    "\n",
    "我们假定有三个不同的操作： 1. 插入新的字符   2. 替换字符   3. 删除一个字符。 每一个操作的代价为1. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(3,\n",
       " [[0, 1, 2, 3, 4, 5],\n",
       "  [1, 0, 1, 2, 3, 4],\n",
       "  [2, 1, 0, 1, 2, 3],\n",
       "  [3, 2, 1, 1, 2, 3]])"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 基于动态规划的解法\n",
    "def edit_dist(str1, str2):\n",
    "    \n",
    "    # m，n分别字符串str1和str2的长度\n",
    "    m, n = len(str1), len(str2)\n",
    "    \n",
    "    # 构建二维数组来存储子问题（sub-problem)的答案 \n",
    "    dp = [[0 for x in range(n+1)] for x in range(m+1)]  # 生成一个二维数组，用0填充\n",
    "#     print(dp)\n",
    "      \n",
    "    # 利用动态规划算法，填充数组\n",
    "    for i in range(m+1): \n",
    "        for j in range(n+1): \n",
    "  \n",
    "            # 假设第一个字符串为空，则转换的代价为j (j次的插入)\n",
    "            if i == 0: \n",
    "                dp[i][j] = j    \n",
    "              \n",
    "            # 同样的，假设第二个字符串为空，则转换的代价为i (i次的插入)\n",
    "            elif j == 0:\n",
    "                dp[i][j] = i\n",
    "            \n",
    "            # 如果最后一个字符相等，就不会产生代价\n",
    "            elif str1[i-1] == str2[j-1]: \n",
    "                dp[i][j] = dp[i-1][j-1] \n",
    "  \n",
    "            # 如果最后一个字符不一样，则考虑多种可能性，并且选择其中最小的值\n",
    "            else: \n",
    "                dp[i][j] = 1 + min(dp[i][j-1],        # Insert \n",
    "                                   dp[i-1][j],        # Remove \n",
    "                                   dp[i-1][j-1])      # Replace \n",
    "  \n",
    "    return dp[m][n], dp \n",
    "n, m= edit_dist('app', 'apble')\n",
    "n, m"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 编辑距离计算\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1, [[0, 1, 2, 3, 4], [1, 0, 1, 2, 3], [2, 1, 0, 1, 2], [3, 2, 1, 0, 1], [4, 3, 2, 1, 0], [5, 4, 3, 2, 1]])\n"
     ]
    }
   ],
   "source": [
    "def generate_edit(str1, str2):\n",
    "    n,m = len(str1), len(str2)\n",
    "    dp = [[0 for i in range(m+1)] for j in range(n+1)]\n",
    "    for i in range(n+1):\n",
    "        for j in range(m+1):\n",
    "            if i == 0:\n",
    "                dp[i][j]=j\n",
    "            elif j==0:\n",
    "                dp[i][j] = i\n",
    "            elif str1[i-1] == str2[j-1]:\n",
    "                dp[i][j] = dp[i-1][j-1]\n",
    "            else:\n",
    "                dp[i][j] = 1 + min(dp[i][j-1],\n",
    "                                  dp[i-1][j],\n",
    "                                  dp[i-1][j-1])\n",
    "    return dp[-1][-1], dp\n",
    "print(generate_edit('apple', 'appl'))\n",
    "            "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 生成指定编辑距离的单词\n",
    "给定一个单词，我们也可以生成编辑距离为K的单词列表。 比如给定 str=\"apple\"，K=1, 可以生成“appl”, \"appla\", \"pple\"...等\n",
    "下面看怎么生成这些单词。 还是用英文的例子来说明。 仍然假设有三种操作 - 插入，删除，替换"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[('', 'apple'), ('a', 'pple'), ('ap', 'ple'), ('app', 'le'), ('appl', 'e'), ('apple', '')]\n",
      "['aapple', 'bapple', 'capple', 'dapple']\n",
      "['pple', 'aple', 'aple', 'appe', 'appl']\n",
      "281\n"
     ]
    }
   ],
   "source": [
    "def generate_edit_one(str):\n",
    "    \"\"\"\n",
    "    给定一个字符串，生成编辑距离为1的字符串列表。\n",
    "    \"\"\"\n",
    "    letters    = 'abcdefghijklmnopqrstuvwxyz'\n",
    "    splits = [(str[:i], str[i:])for i in range(len(str)+1)]\n",
    "    print(splits)\n",
    "    inserts = [L + c + R for L, R in splits for c in letters]\n",
    "    print(inserts[0:4])\n",
    "    deletes = [L + R[1:] for L, R in splits if R]\n",
    "    print(deletes)\n",
    "    replaces = [L + c + R[1:] for L, R in splits if R for c in letters]\n",
    "    \n",
    "    #return set(splits)\n",
    "    return set(inserts + deletes + replaces)\n",
    "\n",
    "print (len(generate_edit_one(\"apple\")))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "230\n"
     ]
    }
   ],
   "source": [
    "\"\"\"\n",
    "    给定一个字符串，生成编辑距离为1的字符串列表\n",
    "\"\"\"\n",
    "def generate_edit_one_sub(str):\n",
    "    \n",
    "    \n",
    "    letters = 'abcdefghijklmnopqrstuvwxyz'\n",
    "    # 切分字符串\n",
    "    splits = [(str[:i], str[i:]) for i in range(len(str)+1)]\n",
    "    # 插入操作\n",
    "    inserts = [l + c + r for l, r in splits for c in letters]\n",
    "    # 删除操作\n",
    "    deletes = [l + r[1:] for l, r in splits]\n",
    "    # 替代操作\n",
    "    replaces = [l + c + r[1:] for l, r in splits for c in letters]\n",
    "    \n",
    "    \n",
    "    return set(inserts + deletes + replaces)\n",
    "print(len(generate_edit_one_sub('appl')))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 生成指定编辑距离k的单词列表"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "230\n"
     ]
    }
   ],
   "source": [
    "def generate_edit_one_1(s):\n",
    "    letters = 'abcdefghijklmnopqrstuvwxyz'\n",
    "#     print(len(letters))\n",
    "    # 字符串切片\n",
    "    splits = [(s[:i], s[i:]) for i in range(len(s)+1)]\n",
    "    # insert \n",
    "    insters = [l + c +r for l, r in splits  for c in letters]\n",
    "    # delete\n",
    "    deteles = [l + r[1:] for l, r in splits if r]\n",
    "    # replace\n",
    "    replaces = [l + c + r[1:] for l, r in splits if r for c in letters]\n",
    "    \n",
    "    return set(insters+deteles+replaces)\n",
    "edits = generate_edit_one_1(\"appl\")\n",
    "print(len(edits))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "59242\n"
     ]
    }
   ],
   "source": [
    "def generate_edit_two(str):\n",
    "    \"\"\"\n",
    "    给定一个字符串，生成编辑距离不大于2的字符串\n",
    "    \"\"\"\n",
    "    edits_1 = list(generate_edit_one_1(str))\n",
    "    edits_2 =[]\n",
    "    for e in edits:\n",
    "        edits_2 = edits_2 + list(generate_edit_one_1(e))\n",
    "    return edits_2\n",
    "print(len(generate_edit_two('apple')))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 基于结巴（jieba）的分词。 Jieba是最常用的中文分词工具~ "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Building prefix dict from the default dictionary ...\n",
      "Dumping model to file cache /var/folders/pr/0fhkrt7s4cj8yygh6m87_1fw0000gn/T/jieba.cache\n",
      "Loading model cost 0.905 seconds.\n",
      "Prefix dict has been built succesfully.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Default Mode: 贪心/ 学院/ 专注/ 于/ 人工智能/ 教育\n",
      "Default Mode: 贪心学院/ 专注/ 于/ 人工智能/ 教育\n"
     ]
    }
   ],
   "source": [
    "# encoding=utf-8\n",
    "import jieba\n",
    "\n",
    "# 基于jieba的分词\n",
    "# cut_all = False为精确模式，默认为精确模式，cut_all = Ture为全模式\n",
    "seg_list = jieba.cut(\"贪心学院专注于人工智能教育\", cut_all=False)\n",
    "print(\"Default Mode: \" + \"/ \".join(seg_list))  \n",
    "\n",
    "jieba.add_word(\"贪心学院\")\n",
    "seg_list = jieba.cut(\"贪心学院专注于人工智能教育\", cut_all=False)\n",
    "print(\"Default Mode: \" + \"/ \".join(seg_list)) \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "default mode: 贪心/ 学院/ 是/ 教育/ 机构\n",
      "default mode贪心学院/ 是/ 教育/ 机构\n"
     ]
    }
   ],
   "source": [
    "import jieba\n",
    "#  基于jieba的分词\n",
    "seg_list = jieba.cut('贪心学院是教育机构', cut_all = False)\n",
    "print('default mode: ' + '/ '.join(seg_list))\n",
    "\n",
    "\n",
    "# 向jieba分词库中加入字典\n",
    "jieba.add_word('贪心学院')\n",
    "seg_list = jieba.cut('贪心学院是教育机构', cut_all=False)\n",
    "print('default mode' + '/ '.join(seg_list))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 判断一句话是否能够切分（被字典）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(False, [True, False, True, False, True, False])\n"
     ]
    }
   ],
   "source": [
    "dic = set([\"贪心科技\", \"人工智能\", \"教育\", \"在线\", \"专注于\"])\n",
    "def word_break(str):\n",
    "    # 创建一个len(str)+1的数组\n",
    "    could_break = [False] * (len(str) + 1)\n",
    "    #  输入为空时，则能被切分\n",
    "    could_break[0] = True\n",
    "\n",
    "    for i in range(1, len(could_break)):\n",
    "        for j in range(0, i):\n",
    "            if str[j:i] in dic and could_break[j] == True:\n",
    "                could_break[i] = True\n",
    "\n",
    "    return could_break[len(str)], could_break\n",
    "print(word_break('在线教育是'))\n",
    "\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [],
   "source": [
    "assert word_break(\"贪心科技在线教育\")==True\n",
    "assert word_break(\"在线教育是\")==False\n",
    "assert word_break(\"\")==True\n",
    "assert word_break(\"在线教育人工智能\")==True\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#  判断一个句子能否被切分"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 79,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "False\n"
     ]
    }
   ],
   "source": [
    "dic = set([\"贪心科技\", \"人工智能\", \"教育\", \"在线\", \"专注于\"])\n",
    "def word_break(str):\n",
    "    # 创建一个len(str) + 1的数组存储\n",
    "    could_break = [False] * (len(str)+1)\n",
    "    # 如果字符串为空， 则直接返回第一个值,为true\n",
    "    could_break[0] = True\n",
    "    for i in range(1, len(could_break)):\n",
    "        for j in range(0, i):\n",
    "            if str[j:i] in dic and could_break[j] == True:\n",
    "                could_break[i] = True\n",
    "    return could_break[len(str)]\n",
    "print(word_break('在线教育是'))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 思考题：给定一个词典和一个字符串，能不能返回所有有效的分割？ （valid segmentation) \n",
    "比如给定词典：dic = set([\"贪心科技\", \"人工智能\", \"教育\", \"在线\", \"专注于\"， “贪心”])\n",
    "和一个字符串 = “贪心科技专注于人工智能”\n",
    "\n",
    "输出为： \n",
    "“贪心” “科技” “专注于” “人工智能”\n",
    "\"贪心科技\" “专注于” “人工智能”"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 65,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[['贪心', '科技', '专注于', '人工智能'], ['贪心科技', '专注于', '人工智能']]\n"
     ]
    }
   ],
   "source": [
    "dic = set([\"贪心科技\", \"人工智能\", \"教育\", \"在线\", \"专注于\",'贪心','科技'])\n",
    "def all_possible_segmentations(str,dic):\n",
    "    def sentences(cur):\n",
    "        \n",
    "        segs = []\n",
    "        if cur< len(str):\n",
    "            for i in range(cur+1,len(str)+1):\n",
    "                if str[cur:i] in dic:\n",
    "                    segs = segs +  [str[cur:i] +(tail and ',' + tail) for tail in sentences(i)]\n",
    "                    \n",
    "        else:\n",
    "            return ['']\n",
    "    \n",
    "        return segs\n",
    "    list_new = []\n",
    "    for line in sentences(0):\n",
    "        line = line.split(',')\n",
    "        list_new.append(line)\n",
    "    return list_new\n",
    "print(all_possible_segmentations('贪心科技专注于人工智能', dic))       "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[['']]\n"
     ]
    }
   ],
   "source": [
    "dic = set([\"贪心科技\", \"人工智能\", \"教育\", \"在线\", \"专注于\",'贪心','科技'])\n",
    "def all_possible_segmentations_1(str, dic):\n",
    "    def sentence(cur):\n",
    "        segs = []\n",
    "        if cur < len(str):\n",
    "            for i in range(cur+1,len(str)+1):\n",
    "                if str[cur:i] in dic:\n",
    "                    segs = segs + [str[cur:i] + (tail and ',' + tail) for tail in sentence(i)]\n",
    "                else:\n",
    "                    return ['']\n",
    "        return segs\n",
    "        \n",
    "    list_new = []  \n",
    "    for line in sentence(0):\n",
    "        line = line.split(',')\n",
    "        list_new.append(line)\n",
    "    return list_new\n",
    "print(all_possible_segmentations_1('贪心科技专注于人工智能', dic))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 停用词过滤\n",
    "出现频率特别高的和频率特别低的词对于文本分析帮助不大，一般在预处理阶段会过滤掉。 \n",
    "在英文里，经典的停用词为 “The”, \"an\".... "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 55,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "['we', 'are', 'students']\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[nltk_data] Error loading stopwords: <urlopen error [Errno 11004]\n",
      "[nltk_data]     getaddrinfo failed>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 55,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 方法1： 自己建立一个停用词词典\n",
    "import jieba\n",
    "stop_words = [\"the\", \"an\", \"is\", \"there\",' ']\n",
    "# 在使用时： 假设 word_list包含了文本里的单词\n",
    "sentence = 'we are the students'\n",
    "word_list = jieba.cut(sentence, cut_all = False)\n",
    "# word_list = ','.join(seg)\n",
    "# print('/'.join(word_list))\n",
    "# word_list = [\"we\", \"are\", \"the\", \"students\"]\n",
    "filtered_words = [word for word in word_list if word not in stop_words]\n",
    "print (filtered_words)\n",
    "\n",
    "# 方法2：直接利用别人已经构建好的停用词库\n",
    "# from nltk.corpus import stopwords\n",
    "import jieba\n",
    "import nltk\n",
    "nltk.download('stopwords')\n",
    "\n",
    "# cachedStopWords = stopwords.words(\"english\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "[nltk_data] Error loading stopwords: <urlopen error [Errno 11004]\n",
      "[nltk_data]     getaddrinfo failed>\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "False"
      ]
     },
     "execution_count": 52,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import nltk\n",
    "nltk.download('stopwords')\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "caress fli die mule deni die agre own humbl size meet state siez item sensat tradit refer colon plot\n"
     ]
    }
   ],
   "source": [
    "from nltk.stem.porter import *\n",
    "stemmer = PorterStemmer()\n",
    "\n",
    "test_strs = ['caresses', 'flies', 'dies', 'mules', 'denied',\n",
    "         'died', 'agreed', 'owned', 'humbled', 'sized',\n",
    "         'meeting', 'stating', 'siezing', 'itemization',\n",
    "         'sensational', 'traditional', 'reference', 'colonizer',\n",
    "         'plotted']\n",
    "\n",
    "singles = [stemmer.stem(word) for word in test_strs]\n",
    "print(' '.join(singles))  # doctest: +NORMALIZE_WHITESPACE"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 词袋向量： 把文本转换成向量 。 只有向量才能作为模型的输入。 "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 方法1： 词袋模型（按照词语出现的个数）\n",
    "from sklearn.feature_extraction.text import CountVectorizer\n",
    "vectorizer = CountVectorizer()\n",
    "corpus = [\n",
    "     'He is going from Beijing to Shanghai.',\n",
    "     'He denied my request, but he actually lied.',\n",
    "     'Mike lost the phone, and phone was in the car.',\n",
    "]\n",
    "X = vectorizer.fit_transform(corpus)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0 0 1 0 0 0 1 1 1 0 1 0 0 0 0 0 0 1 0 1 0]\n",
      " [1 0 0 1 0 1 0 0 2 0 0 1 0 0 1 0 1 0 0 0 0]\n",
      " [0 1 0 0 1 0 0 0 0 1 0 0 1 1 0 2 0 0 2 0 1]]\n",
      "['actually', 'and', 'beijing', 'but', 'car', 'denied', 'from', 'going', 'he', 'in', 'is', 'lied', 'lost', 'mike', 'my', 'phone', 'request', 'shanghai', 'the', 'to', 'was']\n"
     ]
    }
   ],
   "source": [
    "print (X.toarray())\n",
    "print (vectorizer.get_feature_names())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 方法2：词袋模型（tf-idf方法）\n",
    "from sklearn.feature_extraction.text import TfidfVectorizer\n",
    "vectorizer = TfidfVectorizer(smooth_idf=False)\n",
    "X = vectorizer.fit_transform(corpus)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0.         0.         0.39379499 0.         0.         0.\n",
      "  0.39379499 0.39379499 0.26372909 0.         0.39379499 0.\n",
      "  0.         0.         0.         0.         0.         0.39379499\n",
      "  0.         0.39379499 0.        ]\n",
      " [0.35819397 0.         0.         0.35819397 0.         0.35819397\n",
      "  0.         0.         0.47977335 0.         0.         0.35819397\n",
      "  0.         0.         0.35819397 0.         0.35819397 0.\n",
      "  0.         0.         0.        ]\n",
      " [0.         0.26726124 0.         0.         0.26726124 0.\n",
      "  0.         0.         0.         0.26726124 0.         0.\n",
      "  0.26726124 0.26726124 0.         0.53452248 0.         0.\n",
      "  0.53452248 0.         0.26726124]]\n",
      "['actually', 'and', 'beijing', 'but', 'car', 'denied', 'from', 'going', 'he', 'in', 'is', 'lied', 'lost', 'mike', 'my', 'phone', 'request', 'shanghai', 'the', 'to', 'was']\n"
     ]
    }
   ],
   "source": [
    "print (X.toarray())\n",
    "print (vectorizer.get_feature_names())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "    "
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
